/*
 * HashExtensibleRssRss.cpp
 *
 *  Created on: 02/05/2014
 *      Author: ariel
 */

#include "HashExtensibleRss.h"

HashExtensibleRss::HashExtensibleRss() {

	FILE* archivo1 = fopen(BLOQUES_HASH_RSS_FILE, "r");
	FILE* archivo2 = fopen(BLOQUES_LIBRES_HASH_RSS_FILE, "r");
	FILE* archivo3 = fopen(TABLA_HASH_RSS_FILE, "r");
	bool existen_archivos = false;
	if (archivo1 != NULL && archivo2 != NULL && archivo3 != NULL){
		existen_archivos = true;
		fclose(archivo1);
		fclose(archivo2);
		fclose(archivo3);
	}

	unsigned int componentSize = 4096;
	this->file_hash = new Archivo(BLOQUES_HASH_RSS_FILE, componentSize);

	unsigned int componentSize2 = 4;
	this->file_freeSpaces = new Archivo(BLOQUES_LIBRES_HASH_RSS_FILE,
			componentSize2);

	unsigned int componentSize3 = 4;
	this->file_table = new Archivo(TABLA_HASH_RSS_FILE,componentSize3);

	if ( existen_archivos )
	{
		//cout<<"existe archivo hash"<<endl;
		this->setTable(this->getTable());
		this->setfreeSpaces(this->getIdblocksLibres());

	}
	else
	{
		//cout<<"NO existe archivo hash"<<endl;
		vector<int> table;
		table.push_back(0);
		this->setTable(table);

		//bloque inicial
		this->agregarEnArchivo(new BloqueRss(0, 1));
	}

	//reinicio de secuencias
	//IDGenerator::getInstance()->resetAll();
}

HashExtensibleRss::~HashExtensibleRss() {
	if ( this->file_hash ) 			delete this->file_hash;
	if ( this->file_freeSpaces ) 	delete this->file_freeSpaces;
	if ( this->file_table ) 		delete this->file_table;
}

vector<int> HashExtensibleRss::getIdblocksLibres() {
	vector<int> nroBlquesLibres;
	unsigned int elementos = this->file_freeSpaces->elementos();

	for (unsigned int i = 0; i < elementos; i++) {
		string nroBloque_str = this->file_freeSpaces->leer(i);
		nroBlquesLibres.push_back(Util::DeSerializeInt(nroBloque_str));
	}

	return nroBlquesLibres;
}

vector<int> HashExtensibleRss::getTable() {

	vector<int> table;
	unsigned int elementos = this->file_table->elementos();

	for (unsigned int i = 0; i < elementos; i++) {
		string nroTable_str = this->file_table->leer(i);
		table.push_back(Util::DeSerializeInt(nroTable_str));
	}

	return table;
}

void HashExtensibleRss::setTable(vector<int> table)
{
	this->file_table->vaciar();

	for (unsigned int i = 0; i < table.size(); i++)
	{
		this->file_table->agregar(Util::SerializeInt(table[i]));
	}
}

void HashExtensibleRss::setfreeSpaces(vector<int> bloquesLibres)
{
	this->file_freeSpaces->vaciar();

	for (unsigned int i = 0; i < bloquesLibres.size(); i++)
	{
		this->file_freeSpaces->agregar(Util::SerializeInt(bloquesLibres[i]));
	}
}

bool HashExtensibleRss::doHashingAdd(Rss* registro) {
	vector<int> table = this->getTable();
	ElementoRss* Elemento_add = new ElementoRss(registro, -1);

	int pos_Bloque_inTable = Utils::buscar_pos_bloque(registro->getId(),table);

	BloqueRss* Bloque_found = this->leerDeArchivo(table[pos_Bloque_inTable]);
	bool hashingAddOK = false;
	if ( Bloque_found != NULL )
	{
		hashingAddOK = true;
		bool redispersar_Bloque_found = this->dispersar(Elemento_add,
				pos_Bloque_inTable, Bloque_found);

		//tomo la nueva tabla que se actualizo dentro del metodo dispersion
		table = this->getTable();

		if (redispersar_Bloque_found == true) {

			vector<ElementoRss*> elementos_redispersar =
					Bloque_found->getelements();
			Bloque_found->markelements_asFree();
			this->updateBlockEnArchivo(Bloque_found);

			for (int i = 0; i < Bloque_found->getCapacity(); i++) {
				ElementoRss* Elemento_redispersar = elementos_redispersar[i];
				int pos_Bloque_inTable = Utils::buscar_pos_bloque(
						Elemento_redispersar->getRegistro()->getId(), table);
				Bloque_found = this->leerDeArchivo(table[pos_Bloque_inTable]);

				if (dispersar(Elemento_redispersar, pos_Bloque_inTable,
						Bloque_found)) {
					cout <<endl<< "Se produjo redispersion en medio de una redispersion"
							<< endl;
				}
			}
		}
	}

	return hashingAddOK;
}

bool HashExtensibleRss::doHashingDelete(Rss* registro) {
	vector<int> table = this->getTable();
	int pos_Bloque_inTable = Utils::buscar_pos_bloque(registro->getId(),table);
	int id_Bloque_found = table[pos_Bloque_inTable];
	BloqueRss* Bloque_found = this->leerDeArchivo(id_Bloque_found);

	bool hashingAddDELETE = false;
	if ( Bloque_found != NULL )
	{
		hashingAddDELETE = true;
		ElementoRss* Elemento_delete = Bloque_found->getElemento(registro);

		Bloque_found->deleteElemento(Elemento_delete);
		this->updateBlockEnArchivo(Bloque_found);

		if (Bloque_found->isFreed()) {
			//this->agregarNroBloqueLibreEnArchivo(id_Bloque_found);

			int tam_dispersion = Bloque_found->getTamDispersion();
			int idBloqueReemplazo = Utils::redifinitionTable_forDeleteOK(
					tam_dispersion, pos_Bloque_inTable, table);

			//modificacion para archivo
			Bloque_found = this->leerDeArchivo(idBloqueReemplazo);
			Bloque_found->reducirMitad_tamDispersion();
		}

		this->updateBlockEnArchivo(Bloque_found);
	}

	return hashingAddDELETE;
}

void HashExtensibleRss::agregarEnArchivo(BloqueRss* bloque) {
	this->file_hash->agregar(bloque->serialize());
}

BloqueRss* HashExtensibleRss::leerDeArchivo(int nroBloque) {
	BloqueRss* bloque = new BloqueRss();
	unsigned int elementos = this->file_hash->elementos();
	if (elementos > 0)
	{
		std::string buffer = this->file_hash->leer(nroBloque);
		bloque->unserialize(buffer);
	}else{
		bloque = NULL;
	}

	return bloque;
}

void HashExtensibleRss::updateBlockEnArchivo(BloqueRss* bloque) {
	this->file_hash->escribir(bloque->serialize(), bloque->getId());
}

void HashExtensibleRss::agregarNroBloqueLibreEnArchivo(int nroBloqueLibre) {
	this->file_freeSpaces->agregar(Util::SerializeInt(nroBloqueLibre));
}

int HashExtensibleRss::leerUnNroBloqueLibre() {

	int nroBloque = -1;

	unsigned int elementos = this->file_freeSpaces->elementos();
	if (elementos > 0) {
		//voy a tomar el ultimo
		string nroBloque_str = this->file_freeSpaces->leer(elementos - 1);
		nroBloque = Util::DeSerializeInt(nroBloque_str);
		//TODO falta eliminarlo fisicamente del archivo
	}

	return nroBloque;
}

void HashExtensibleRss::reportBloques() {
	for (unsigned i = 0; i < this->file_hash->elementos(); i++) {
		cout << "\t" << *leerDeArchivo(i);
	}
}

bool HashExtensibleRss::dispersar(ElementoRss* Elemento_add,
		int pos_Bloque_inTable, BloqueRss* Bloque_found) {
	vector<int> table = this->getTable();
	int id_Bloque_inc = this->file_hash->elementos();

	bool redispersar_Bloque_found = false;

	if (!Bloque_found->existElemento(Elemento_add->getRegistro())) {
		if (Bloque_found->isBusy()) {
			int id_Bloque_new = id_Bloque_inc++;

			int tam_dispersion = Bloque_found->getTamDispersion();

			//duplicate tam dispersion
			Bloque_found->duplicate_tamDispersion();
			int tam_dispersion_new = Bloque_found->getTamDispersion();

			// create new Bloque
			BloqueRss* Bloque_new = new BloqueRss(id_Bloque_new,
					tam_dispersion_new);
			Bloque_new->addElemento(Elemento_add);
			this->agregarEnArchivo(Bloque_new);

			if ((int) table.size() == tam_dispersion) {
				vector<int> tableNew = Utils::dulplicate_table(table);
				tableNew[pos_Bloque_inTable] = id_Bloque_new;
				this->setTable(tableNew);
			} else //if tam_table is not equal tam dispersion
			{
				this->setTable(
						Utils::redifinir_table(table, tam_dispersion_new,
								pos_Bloque_inTable, id_Bloque_new));
			}

			redispersar_Bloque_found = true;
		} else {
			Bloque_found->addElemento(Elemento_add);
		}

	} else {

		if (Elemento_add->isFreed()) {
			Bloque_found->markElemento_asBusy(Elemento_add);
		}

		//TODO ver que pasa cuando el rss por error viene repetido y marcado como busy

	}

	if (!redispersar_Bloque_found) {
		this->updateBlockEnArchivo(Bloque_found);
	}

	return redispersar_Bloque_found;
}

Rss* HashExtensibleRss::searchRss(Rss* registro) {
	BloqueRss* bloque = this->search(registro);
	if (bloque != NULL) {
		ElementoRss* elemento = bloque->getElemento(registro);

		return elemento->getRegistro();
	}
	return NULL;
}

BloqueRss* HashExtensibleRss::search(Rss* registro) {
	vector<int> table = this->getTable();
	int pos_Bloque_inTable = Utils::buscar_pos_bloque(registro->getId(),table);
	BloqueRss* bloque_found = this->leerDeArchivo(table[pos_Bloque_inTable]);

	bool existElemento = bloque_found->existElemento(registro);
	if (!existElemento
			|| (existElemento
					&& bloque_found->getElemento(registro)->isDeleted())) {
		bloque_found = NULL;
	}

	return (bloque_found);
}

Archivo* HashExtensibleRss::getFileFreeSpaces() {
	return this->file_freeSpaces;
}

Archivo* HashExtensibleRss::getFileHash() {
	return this->file_hash;
}

Archivo* HashExtensibleRss::getFileTable() {
	return this->file_table;
}

void HashExtensibleRss::exportar()
{
	Utils::coutVector("Tabla hash RSS",this->getTable());
	this->reportBloques();
}
